package nave.control;

import java.util.ArrayList;
import java.util.Random;

import nave.Constants;
import nave.Point;
import nave.model.Enemy;
import nave.model.Explosion;
import nave.model.GameObject;
import nave.model.Player;
import nave.model.Shot;
import nave.model.Spaceship;
import nave.model.Stage;

public class Simulation {
	
	/*
	 * Attributes
	 */
	private static Simulation singleton = null;
	
	private static int stageType = Stage.STAGES_RANDOM;
	private static ViewActions view = null;
	private static Random randomGenerator = new Random();
	
	private Player player = null;
	private int enemyCount = 0;

	private static ArrayList<GameObject> objects = new ArrayList<GameObject>();

	private int stageNumber = 1;

	
	/*
	 * Constructor
	 */
	private Simulation (int stageType) {
		
		// TODO define the size of the matrix
		new Stage(Constants.STAGE_ROWS, Constants.STAGE_COLS, stageType);
		Simulation.stageType = stageType;

		Constants.TILE_HEIGHT = Constants.SCREEN_HEIGHT / Stage.getRows();
		Constants.TILE_WIDTH = Constants.SCREEN_WIDTH / Stage.getCols();
		
		Shot.HEIGHT = Constants.TILE_HEIGHT / 4;
		Shot.WIDTH = Constants.TILE_WIDTH / 4;

		player = new Player(Options.getLives(), Options.getNumberOfPlayerBullets());

		objects.add(player);

		populate();
	}

	
	/*
	 * Getter and Setter for View
	 */
	public static void startSimulation(int stageType) {
		singleton = new Simulation(stageType);
	}
	public static Simulation getSimulation() {
		if ( singleton == null )
			startSimulation(stageType);
		return singleton;
	}
	public ArrayList<GameObject> getObjects() {
		return objects;
	}
	public void setSimulationFunctions(ViewActions listener) {
		Simulation.view = listener;
	}

	
	/*
	 * Public methods
	 */
	public void update (float delta) {

		item(); // TODO item acquire

		player.update(delta);

		for (int i = 0; i < objects.size(); i++) {
			GameObject obj = objects.get(i);

			if (obj instanceof Enemy) updateEnemy(delta, (Enemy)obj);
			else if (obj instanceof Shot) updateShot(delta, (Shot)obj);
			else if (obj instanceof Explosion) updateExplosion(delta, (Explosion)obj);	
//			else if (obj instanceof Item) updateItem(delta, (Item)obj); // TODO
		}

		checkNextLevel();
		checkEndGame();
	}
	public void move(int direction) {
		player.setDirection(direction);
	}
	public void shot() {
		Shot s = player.shot();
		if ( s != null ) {
			objects.add(s);
			if ( view != null ) view.shot();
		}
	}
	public int score() {
		return player.getScore();
	}


	/*
	 * Private methods
	 */
	private void populate () {

		Enemy.resetIdCounter();
		enemyCount = Options.getNumberOfEnemies();

		new Thread(new Runnable() {
			@Override
			public void run() {
				
				int numberOfEnemies = Options.getNumberOfEnemies();
				int sleep = Constants.ENEMY_ENTRY_DELAY * 1000;

				while ( numberOfEnemies > 0 )
				{
					Enemy e = new Enemy( Options.getNumberOfEnemiesBullets() );
					Simulation.objects.add( e );
					numberOfEnemies--;
					try
					{
						Thread.sleep(sleep);
					} catch (InterruptedException err) {
						err.printStackTrace();
					}
				}
			}
		}).start();
	}

	private void item() {
		// TODO place the right numbers
		if( randomGenerator.nextInt(12341) == 0 ) // here
		{
//			objects.add(new Item(randomGenerator.nextInt(0))); // and here
		}
	}

	private void checkNextLevel () {
		if ( enemyCount <= 0 && objects.size() <= 1 ) {

			Player.score(Constants.POINTS_NEXT_STAGE + stageNumber * 5);
			if (view != null) view.nextStage();
			
			// TODO DEBUG put a decent next stage animation here
			try {
				Thread.sleep(2500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			Stage.nextStage(stageType);

			player.replace();
			populate();

			stageNumber++;
		}
	}

	private void checkEndGame() {
		if ( player.getLives() <= 0 ) {
			if (view != null) view.endGame();
			try
			{
				this.finalize();
			}
			catch (Throwable e)
			{
				e.printStackTrace();
			}
		}
	}

	private void updateEnemy (float delta, Enemy e) {
		e.update(delta);
		Shot s = e.intelligence(stageNumber);
		
		if ( s != null ) {
			objects.add(s);
			if ( view != null ) view.shot();
		}
	}

	private void updateShot (float delta, Shot s) {
		s.update(delta);
		checkCollision(s);
		if (s.hasLeftBounds()) objects.remove(s);
	}

	private void updateExplosion (float delta, Explosion e) {
		e.update(delta);
		if (e.getAliveTime() > Explosion.EXPLOSION_TIME) objects.remove(e);
	}

	private void checkCollision (Shot shot) {
		int place = Stage.getPositionOnStage(shot.getPosition());

		if ( place == Constants.EMPTY || place == shot.getParent().getId() )
			return;

		if ( place == Constants.WALL )
		{
			shot.collided();
			return;
		}

		Spaceship target = null;
		
		for (int i = 0; i < objects.size(); i++) {
			GameObject g = objects.get(i);
			
			if ( g instanceof Spaceship ) {
				
				target = (Spaceship) g;
			
				if ( target.getId() == place )
					break;
			}
		}

		if ( hit(shot.getHead(), target) )
		{
			shot.collided();
			
			if ( target.getStatus() != Spaceship.STATUS_INVENCIBLE ) {
				Explosion e = new Explosion(target);
				objects.add(e);
				
				target.explode();
				
				objects.remove(target);

				if ( target instanceof Player ) {
					new Thread() {
						public void run() {
							while ( player.isExploding )
								;
							objects.add(player);
						};
					}.start();
				}
				if ( target instanceof Enemy ) // TODO will it be necessary?
					enemyCount--;

				if ( view != null ) view.explosion();
			}
		}
	}

	private boolean hit(Point p, Spaceship s) {
		int x = s.getDrawPositon().x;
		int y = s.getDrawPositon().y;

		float offset = 0.05f;

		float padx = (Constants.TILE_WIDTH * offset);
		float pady = (Constants.TILE_HEIGHT * offset);

		if ( (p.x > x + padx) && (p.x < x + Constants.TILE_WIDTH - padx) &&
				(p.y > y + pady) && (p.y < y + Constants.TILE_HEIGHT - pady) )
			return true;

		return false;
	}

}
